package dao

import (
	"context"
	"database/sql"
	"fmt"
	"math"
	"strings"

	"probe-scheduler/internal/config"
	"probe-scheduler/internal/model"
	"probe-scheduler/pb/scheduler"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// TaskDao 任务数据访问层
type TaskDao struct {
	db *gorm.DB
}

// NewTaskDao 初始化TaskDao（连接SQLite并自动建表）
func NewTaskDao(c config.SqliteConfig) (*TaskDao, error) {
	// 连接SQLite（文件不存在会自动创建）
	db, err := gorm.Open(sqlite.Open(c.Dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), // 开启SQL日志（开发环境）
	})
	if err != nil {
		return nil, fmt.Errorf("sqlite连接失败: %w", err)
	}

	// 自动迁移表结构（会添加自增和唯一索引）
	if err := db.AutoMigrate(&model.TaskModel{}); err != nil {
		return nil, fmt.Errorf("表迁移失败: %w", err)
	}

	return &TaskDao{db: db}, nil
}

// CreateTask 创建任务（返回数据库自增ID）
func (d *TaskDao) CreateTask(ctx context.Context, task *scheduler.Task) (int64, error) {
	model := &model.TaskModel{
		// ID无需赋值，数据库自动生成
		Name:           task.Name,
		TaskType:       int32(task.Type),
		ProbeType:      int32(task.ProbeConfig.Type),
		ProbeTarget:    task.ProbeConfig.Target,
		ProbeTimeout:   task.ProbeConfig.Timeout,
		CronExpression: task.CronExpression,
		Interval:       task.Interval,
		StartTime:      task.StartTime,
		EndTime:        task.EndTime,
		Status:         int32(task.Status),
	}

	// 执行创建，捕获唯一约束冲突错误
	if err := d.db.WithContext(ctx).Create(model).Error; err != nil {
		return 0, d.handleUniqueConflictError(err)
	}

	// 返回数据库生成的自增ID
	return model.ID, nil
}

// DeleteTask 删除任务（软删除，taskID改为int64）
func (d *TaskDao) DeleteTask(ctx context.Context, taskID int64) error {
	return d.db.WithContext(ctx).Delete(&model.TaskModel{}, "id = ?", taskID).Error
}

// UpdateTask 更新任务（taskID改为int64，处理唯一约束）
func (d *TaskDao) UpdateTask(ctx context.Context, task *scheduler.Task) error {
	if task.Id == 0 {
		return fmt.Errorf("任务ID不能为空")
	}

	model := &model.TaskModel{
		ID:             task.Id,
		Name:           task.Name,
		TaskType:       int32(task.Type),
		ProbeType:      int32(task.ProbeConfig.Type),
		ProbeTarget:    task.ProbeConfig.Target,
		ProbeTimeout:   task.ProbeConfig.Timeout,
		CronExpression: task.CronExpression,
		Interval:       task.Interval,
		StartTime:      task.StartTime,
		EndTime:        task.EndTime,
		Status:         int32(task.Status),
	}

	// 执行更新，捕获唯一约束冲突错误
	if err := d.db.WithContext(ctx).Save(model).Error; err != nil {
		return d.handleUniqueConflictError(err)
	}
	return nil
}

// QueryTask 普通查询（taskID改为int64）
func (d *TaskDao) QueryTask(ctx context.Context, taskID int64, status []scheduler.TaskStatus) ([]*scheduler.Task, int32, error) {
	var models []*model.TaskModel
	tx := d.db.WithContext(ctx).Model(&model.TaskModel{})

	// 过滤条件：taskID=0表示查询所有
	if taskID != 0 {
		tx = tx.Where("id = ?", taskID)
	}
	if len(status) > 0 {
		statusInts := make([]int32, len(status))
		for i, s := range status {
			statusInts[i] = int32(s)
		}
		tx = tx.Where("status IN (?)", statusInts)
	}

	// 统计总数
	var total int64
	if err := tx.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	// 查询数据
	if err := tx.Find(&models).Error; err != nil {
		return nil, 0, err
	}

	// 模型转换：TaskModel -> scheduler.Task
	return modelToPb(models), int32(total), nil
}

// PageQueryTask 分页查询任务（适配int64 ID）
func (d *TaskDao) PageQueryTask(
	ctx context.Context,
	page, pageSize int32,
	status []scheduler.TaskStatus,
	sortBy, sortOrder string,
) ([]*scheduler.Task, int32, int32, error) {
	var models []*model.TaskModel
	tx := d.db.WithContext(ctx).Model(&model.TaskModel{})

	// 1. 过滤条件（状态）
	if len(status) > 0 {
		statusInts := make([]int32, len(status))
		for i, s := range status {
			statusInts[i] = int32(s)
		}
		tx = tx.Where("status IN (?)", statusInts)
	}

	// 2. 排序配置（新增id排序支持）
	if sortBy == "" {
		sortBy = "updated_at"
	}
	if sortOrder == "" || (sortOrder != "asc" && sortOrder != "desc") {
		sortOrder = "desc"
	}
	// 校验允许的排序字段（增加id）
	allowedSortFields := map[string]bool{"id": true, "created_at": true, "updated_at": true, "name": true, "status": true}
	if !allowedSortFields[sortBy] {
		sortBy = "updated_at"
	}
	tx = tx.Order(fmt.Sprintf("%s %s", sortBy, sortOrder))

	// 3. 统计总数
	var total int64
	if err := tx.Count(&total).Error; err != nil {
		return nil, 0, 0, err
	}

	// 4. 分页计算
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 10
	}
	if pageSize > 100 {
		pageSize = 100
	}
	offset := (page - 1) * pageSize

	// 5. 分页查询
	if err := tx.Offset(int(offset)).Limit(int(pageSize)).Find(&models).Error; err != nil {
		return nil, 0, 0, err
	}

	// 6. 计算总页数
	totalPage := int32(math.Ceil(float64(total) / float64(pageSize)))

	// 7. 模型转换
	return modelToPb(models), int32(total), totalPage, nil
}

// ListValidTasks 查询所有有效任务（适配int64 ID）
func (d *TaskDao) ListValidTasks(ctx context.Context) ([]*scheduler.Task, error) {
	var models []*model.TaskModel
	// 过滤条件：未软删除 + 状态不是STOPPED
	err := d.db.WithContext(ctx).
		Where("deleted_at IS NULL").
		Where("status IN (?, ?)",
			int32(scheduler.TaskStatus_SCHEDULED),
			int32(scheduler.TaskStatus_PAUSED),
		).
		Find(&models).Error
	if err != nil {
		return nil, err
	}
	return modelToPb(models), nil
}

// PauseTask 暂停任务（taskID改为int64）
func (d *TaskDao) PauseTask(ctx context.Context, taskID int64) error {
	return d.db.WithContext(ctx).
		Model(&model.TaskModel{}).
		Where("id = ?", taskID).
		Update("status", int32(scheduler.TaskStatus_PAUSED)).Error
}

// ResumeTask 恢复任务（taskID改为int64）
func (d *TaskDao) ResumeTask(ctx context.Context, taskID int64) error {
	return d.db.WithContext(ctx).
		Model(&model.TaskModel{}).
		Where("id = ?", taskID).
		Update("status", int32(scheduler.TaskStatus_SCHEDULED)).Error
}

// modelToPb 批量转换：model.TaskModel -> pb.Task（适配int64 ID）
func modelToPb(models []*model.TaskModel) []*scheduler.Task {
	tasks := make([]*scheduler.Task, 0, len(models))
	for _, m := range models {
		tasks = append(tasks, &scheduler.Task{
			Id:   m.ID, // int64自增ID
			Name: m.Name,
			Type: scheduler.TaskType(m.TaskType),
			ProbeConfig: &scheduler.ProbeConfig{
				Type:    scheduler.ProbeType(m.ProbeType),
				Target:  m.ProbeTarget,
				Timeout: m.ProbeTimeout,
			},
			CronExpression: m.CronExpression,
			Interval:       m.Interval,
			StartTime:      m.StartTime,
			EndTime:        m.EndTime,
			Status:         scheduler.TaskStatus(m.Status),
		})
	}
	return tasks
}

// 返回：成功删除数、失败详情（key：任务ID，value：失败原因）、错误
func (d *TaskDao) BatchDeleteTask(ctx context.Context, taskIDs []int64) (int32, map[int64]string, error) {
	if len(taskIDs) == 0 {
		return 0, nil, fmt.Errorf("任务ID列表不能为空")
	}
	// 筛选出实际存在的任务ID（忽略不存在/已删除的）
	existingIDs, err := d.FilterExistingTaskIDs(ctx, taskIDs)
	if err != nil {
		return 0, nil, err
	}
	if len(existingIDs) == 0 {
		return 0, nil, nil // 没有存在的任务，直接返回成功数0
	}

	successCount := int32(0)
	failDetails := make(map[int64]string)

	// 循环删除（确保每个任务的删除状态可追踪，避免单个失败导致整体失败）
	for _, taskID := range existingIDs {
		err := d.db.WithContext(ctx).Delete(&model.TaskModel{}, "id = ?", taskID).Error
		if err != nil {
			// 记录失败详情
			failDetails[taskID] = fmt.Sprintf("删除失败: %v", err)
		} else {
			successCount++
		}
	}

	// 若所有任务都失败，返回整体错误；否则返回部分成功结果
	if successCount == 0 && len(failDetails) > 0 {
		return successCount, failDetails, fmt.Errorf("所有任务删除失败")
	}

	return successCount, failDetails, nil
}

// FilterExistingTaskIDs 筛选出数据库中实际存在（未被软删除）的任务ID
func (d *TaskDao) FilterExistingTaskIDs(ctx context.Context, taskIDs []int64) ([]int64, error) {
	if len(taskIDs) == 0 {
		return nil, nil
	}

	var existingIDs []int64
	// 查询条件：id在列表中 + 未被软删除
	err := d.db.WithContext(ctx).
		Model(&model.TaskModel{}).
		Where("id IN (?)", taskIDs).
		Where("deleted_at IS NULL").
		Pluck("id", &existingIDs).
		Error

	if err != nil {
		return nil, fmt.Errorf("查询存在的任务ID失败: %w", err)
	}

	return existingIDs, nil
}

// handleUniqueConflictError 处理唯一约束冲突错误（SQLite错误码19）
func (d *TaskDao) handleUniqueConflictError(err error) error {
	// SQLite唯一约束冲突错误信息格式：UNIQUE constraint failed: tasks.name
	if err.Error() == sql.ErrNoRows.Error() {
		return err
	}

	msg := err.Error()
	switch {
	case strings.Contains(msg, "tasks.name"):
		return fmt.Errorf("任务名称已存在，请更换名称")
	case strings.Contains(msg, "tasks.probe_target"):
		return fmt.Errorf("探测目标已存在，请更换目标")
	default:
		return fmt.Errorf("数据已存在，无法重复创建: %w", err)
	}
}
